﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.ComponentModel;
	using System.Globalization;
	using System.Web.UI;

	/// <summary>
	/// Server control used to reference blockUI define default page settings for further client-side use.
	/// </summary>
	[ParseChildren(true), PersistChildren(false)]
	public class BlockUi : JQueryControl, IThemed {


		#region Constants: Initialization option defaults

		private const string defaultMessage = "<h1>Please wait...</h1>";
		private const int defaultBaseZ = 1000;
		private const bool defaultCenterX = true;
		private const bool defaultCenterY = true;
		private const bool defaultAllowBodyStretch = true;
		private const bool defaultConstrainTabKey = true;
		private const int defaultFadeIn = 200;
		private const int defaultFadeOut = 400;
		private const int defaultTimeout = 0;
		private const bool defaultShowOverlay = true;
		private const bool defaultFocusInput = true;
		private const bool defaultApplyPlatformOpacityRules = true;

		#endregion


		#region Static Constants: Theme property defaults

		private const bool defaultUseTheme = false;
		private const PanelType defaultPanelType = PanelType.Header;
		private const bool defaultHighlight = false;
		private const bool defaultShowErrorState = false;
		private const Priority defaultPriority = Priority.None;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.BlockUi class.
		/// </summary>
		public BlockUi() { }

		#endregion


		#region Properties: Initialization options

		//TODO: Can we make this a literally an APSX template?  Also, we should use it to exercise the JavaScript encoding..
		/// <summary>
		/// Gets or sets the HTML content displayed when blocking.
		/// HTML is accepted, so HTML encoding is required!
		/// This property maps directly to the "message" default value.
		/// </summary>
		[DefaultValue(defaultMessage), Category("Appearance"), PersistenceMode(PersistenceMode.InnerProperty)]
		public virtual string Message {
			get { return ViewState.GetValue("Message", defaultMessage); }
			set { ViewState["Message"] = value; }
		}

		/// <summary>
		/// Gets or sets the z-index for the blocking overlay.
		/// This property maps directly to the "baseZ" default value.
		/// </summary>
		[DefaultValue(defaultBaseZ), Category("Layout")] //TODO: Is Layout correct for a z-order?
		public virtual int BaseZ {
			get { return ViewState.GetValue("BaseZ", defaultBaseZ); }
			set { ViewState["BaseZ"] = value; }
		}

		/// <summary>
		/// Gets or sets whether the message automatically centered horizontally.  This only affects element blocking, as page blocking is handled through CSS.
		/// This property maps directly to the "centerX" default value.
		/// </summary>
		[DefaultValue(defaultCenterX), Category("Appearance")]
		public virtual bool CenterX {
			get { return ViewState.GetValue("CenterX", defaultCenterX); }
			set { ViewState["CenterX"] = value; }
		}

		/// <summary>
		/// Gets or sets whether the message automatically centered vertically.
		/// This property maps directly to the "centerY" default value.
		/// </summary>
		[DefaultValue(defaultCenterY), Category("Appearance")]
		public virtual bool CenterY {
			get { return ViewState.GetValue("CenterY", defaultCenterY); }
			set { ViewState["CenterY"] = value; }
		}

		/// <summary>
		/// Gets or sets whether to allow body element to be stetched in IE6 to make blocking look better on short pages.  Set to false if you wish to prevent changes to the body height.
		/// This property maps directly to the "allowBodyStretch" default value.
		/// </summary>
		[DefaultValue(defaultAllowBodyStretch), Category("Behavior")]
		public virtual bool AllowBodyStretch {
			get { return ViewState.GetValue("AllowBodyStretch", defaultAllowBodyStretch); }
			set { ViewState["AllowBodyStretch"] = value; }
		}

		/// <summary>
		/// Gets or sets whether blockUI will supress tab navigation from leaving blocking content.
		/// This property maps directly to the "constrainTabKey" default value.
		/// </summary>
		[DefaultValue(defaultConstrainTabKey), Category("Behavior")]
		public virtual bool ConstrainTabKey {
			get { return ViewState.GetValue("ConstrainTabKey", defaultConstrainTabKey); }
			set { ViewState["ConstrainTabKey"] = value; }
		}

		/// <summary>
		/// Gets or sets the fade in time (in milliseconds).  Set to 0 to disable fade in on block.
		/// This property maps directly to the "fadeIn" default value.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value cannot be negative.</exception>
		[DefaultValue(defaultFadeIn), Category("Appearance")]
		public virtual int FadeIn {
			get { return ViewState.GetValue("FadeIn", defaultFadeIn); }
			set {
				if(value < 0) throw new ArgumentOutOfRangeException("value", "Value cannot be negative.");
				ViewState["FadeIn"] = value; 
			}
		}

		/// <summary>
		/// Gets or sets the fade out time (in milliseconds).  Set to 0 to disable fade out on unblock.
		/// This property maps directly to the "fadeOut" default value.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value cannot be negative.</exception>
		[DefaultValue(defaultFadeOut), Category("Appearance")]
		public virtual int FadeOut {
			get { return ViewState.GetValue("FadeOut", defaultFadeOut); }
			set {
				if(value < 0) throw new ArgumentOutOfRangeException("value", "Value cannot be negative.");
				ViewState["FadeOut"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the time (in milliseconds) to wait before auto-unblocking.  Set to 0 to disable auto-unblock.
		/// This property maps directly to the "timeout" default value.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">Value cannot be negative.</exception>
		[DefaultValue(defaultTimeout), Category("Appearance")]
		public virtual int Timeout {
			get { return ViewState.GetValue("Timeout", defaultTimeout); }
			set {
				if(value < 0) throw new ArgumentOutOfRangeException("value", "Value cannot be negative.");
				ViewState["Timeout"] = value;
			}
		}

		/// <summary>
		/// Gets or set whether or not to show a visual overlay.
		/// This property maps directly to the "showOverlay" default value.
		/// </summary>
		[DefaultValue(defaultShowOverlay), Category("Appearance")]
		public virtual bool ShowOverlay {
			get { return ViewState.GetValue("ShowOverlay", defaultShowOverlay); }
			set { ViewState["ShowOverlay"] = value; }
		}

		/// <summary>
		/// Gets or sets whether or not focus will be placed in the first available input field when page blocking.
		/// This property maps directly to the "focusInput" default value.
		/// </summary>
		[DefaultValue(defaultFocusInput), Category("Behavior")]
		public virtual bool FocusInput {
			get { return ViewState.GetValue("FocusInput", defaultFocusInput); }
			set { ViewState["FocusInput"] = value; }
		}

		/// <summary>
		/// Gets or sets whether or not to use overlay styles on Firefox for Linux due to performance issues with opacity.
		/// This property maps directly to the "applyPlatformOpacityRules" default value.
		/// </summary>
		[DefaultValue(defaultApplyPlatformOpacityRules), Category("Behavior")]
		public virtual bool ApplyPlatformOpacityRules {
			get { return ViewState.GetValue("ApplyPlatformOpacityRules", defaultApplyPlatformOpacityRules); }
			set { ViewState["ApplyPlatformOpacityRules"] = value; }
		}

		#endregion


		#region Properties: Theme

		/// <summary>
		/// Gets or sets whether or not the control should be rendered with jQuery UI theme classes.
		/// </summary>
		[DefaultValue(defaultUseTheme), Category("Appearance")]
		public virtual bool UseTheme {
			get { return ViewState.GetValue("UseTheme", defaultUseTheme); }
			set { ViewState["UseTheme"] = value; }
		}

		/// <summary>
		/// Gets or sets a value representing logical jQuery UI component containers defined by the theme framework.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		/// <seealso cref="UseTheme"/>
		[DefaultValue(defaultPanelType), Category("Appearance")]
		public PanelType PanelType {
			get { return ViewState.GetValue("PanelType", defaultPanelType); }
			set {
				if(!Enum.IsDefined(typeof(PanelType), value)) throw new ArgumentException("Value not defined.", "value");

				ViewState["PanelType"] = value;
			}
		}

		/// <summary>
		/// Gets or sets whether or not the control should be rendered with a highlighted interaction cue defined by the theme framework.
		/// </summary>
		/// <seealso cref="UseTheme"/>
		[DefaultValue(defaultHighlight), Category("Appearance")]
		public virtual bool Highlight {
			get { return ViewState.GetValue("Highlight", defaultHighlight); }
			set { ViewState["Highlight"] = value; }
		}

		/// <summary>
		/// Gets or sets whether or not the control should be rendered with an error interaction cue defined by the theme framework.
		/// </summary>
		/// <seealso cref="UseTheme"/>
		[DefaultValue(defaultShowErrorState), Category("Appearance")]
		public virtual bool ShowErrorState {
			get { return ViewState.GetValue("ShowErrorState", defaultShowErrorState); }
			set { ViewState["ShowErrorState"] = value; }
		}

		/// <summary>
		/// Gets or sets a value representing priority-based interaction cues defined by the theme framework.
		/// </summary>
		/// <exception cref="ArgumentException">Value not defined.</exception>
		/// <seealso cref="UseTheme"/>
		[DefaultValue(defaultPriority), Category("Appearance")]
		public virtual Priority Priority {
			get { return ViewState.GetValue("Priority", defaultPriority); }
			set {
				if(!Enum.IsDefined(typeof(Priority), value)) throw new ArgumentException("Value not defined.", "value");

				ViewState["Priority"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the Cascading Style Sheet (CSS) class rendered by the web server control on the client.
		/// </summary>
		[DefaultValue(""), Category("Appearance")]
		public virtual string CssClass {
			get { return ViewState.GetValue("CssClass", string.Empty); }
			set { ViewState["CssClass"] = value; }
		}

		#endregion


		#region Properties: Protected explicit implementation

		/// <summary>
		/// Gets whether or not the control should be rendered with a disabled interaction cue defined by the theme framework.
		/// </summary>
		protected virtual bool Enabled {
			get { return true; }
		}

		#endregion


		#region Methods: Render hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.BlockUiScriptFile);

			manager.RegisterStyleFile(Settings.Default.UiCoreStyleFile);
			manager.RegisterThemeStyleFile();

			manager.RegisterControl(this);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {

			bool masterUseCssStyling = MasterUseCssStyling;
			bool useTheme = UseTheme;

			string cssClass = this.GetThemedCssClass();

			string message = Message;
			if(!string.IsNullOrEmpty(message) && !string.IsNullOrEmpty(cssClass))
				message = string.Format(CultureInfo.InvariantCulture, "<div class=\"{0}\">{1}</div>", cssClass, message);
			if(message != defaultMessage) RenderDefaultValue(script, "message", () => script.AppendValue(message));

			if(masterUseCssStyling) {
				RenderDefaultValue(script, "css", () => script.Append("{}"));
				RenderDefaultValue(script, "overlayCSS", () => script.Append("{}"));
				RenderDefaultValue(script, "growlCSS", () => script.Append("{}"));
			} else if(useTheme) {
				RenderDefaultValue(script, "css.color", () => script.AppendValue(string.Empty));
				RenderDefaultValue(script, "css.border", () => script.AppendValue(string.Empty));
				RenderDefaultValue(script, "css.backgroundColor", () => script.AppendValue(string.Empty));
				RenderDefaultValue(script, "growlCSS.color", () => script.AppendValue(string.Empty));
				RenderDefaultValue(script, "growlCSS.border", () => script.AppendValue(string.Empty));
				RenderDefaultValue(script, "growlCSS.backgroundColor", () => script.AppendValue(string.Empty));
			}

			int baseZ = BaseZ;
			if(baseZ != defaultBaseZ) RenderDefaultValue(script, "baseZ", () => script.AppendValue(baseZ));

			bool centerX = CenterX;
			if(centerX != defaultCenterX) RenderDefaultValue(script, "centerX", () => script.AppendValue(centerX));

			bool centerY = CenterY;
			if(centerY != defaultCenterY) RenderDefaultValue(script, "centerY", () => script.AppendValue(centerY));

			bool allowBodyStretch = AllowBodyStretch;
			if(allowBodyStretch != defaultAllowBodyStretch) RenderDefaultValue(script, "allowBodyStretch", () => script.AppendValue(allowBodyStretch));

			int fadeIn = FadeIn;
			if(fadeIn != defaultFadeIn) RenderDefaultValue(script, "fadeIn", () => script.AppendValue(fadeIn));

			int fadeOut = FadeOut;
			if(fadeOut != defaultFadeOut) RenderDefaultValue(script, "fadeOut", () => script.AppendValue(fadeOut));

			int timeout = Timeout;
			if(timeout != defaultTimeout) RenderDefaultValue(script, "timeout", () => script.AppendValue(timeout));

			bool showOverlay = ShowOverlay;
			if(showOverlay != defaultShowOverlay) RenderDefaultValue(script, "showOverlay", () => script.AppendValue(showOverlay));

			bool focusInput = FocusInput;
			if(focusInput != defaultFocusInput) RenderDefaultValue(script, "focusInput", () => script.AppendValue(focusInput));

			bool applyPlatformOpacityRules = ApplyPlatformOpacityRules;
			if(applyPlatformOpacityRules != defaultApplyPlatformOpacityRules) RenderDefaultValue(script, "applyPlatformOpacityRules", () => script.AppendValue(applyPlatformOpacityRules));

		}

		/// <summary>
		/// A delegate required to render the actual literal value to the client when calling RenderDefaultValue()
		/// </summary>
		protected delegate void AppendValue();

		/// <summary>
		/// Renders the client-side jQuery assignation of a default setting.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		/// <param name="name">The name of the variable to initialize.</param>
		/// <param name="appendValue">A delegate required to render the actual literal value to the client.</param>
		protected virtual void RenderDefaultValue(ScriptBuilder script, string name, AppendValue appendValue) {
			if(script == null) throw new ArgumentNullException("script");
			if(name == null) throw new ArgumentNullException("name");
			if(name.Length == 0) throw new ArgumentException("A value is required.", "name");
			if(appendValue == null) throw new ArgumentNullException("appendValue");

			if(script.Length != 0) script.Append(" ");

			script.Append("$.blockUI.defaults.");
			script.Append(name);
			script.Append("=");
			appendValue();
			script.Append(";");
		}

		#endregion


		#region Explicit Implementation: IThemed

		bool IThemed.Enabled {
			get { return Enabled; }
		}

		#endregion


	}

}